package main

import (
	"fmt"
	"github.com/go-redis/redis"
	"math/rand"
	"time"
)

/*
调研一下 分布式环境中的多个实例的相同定时任务在同一时间点重复执行的问题，同时执行时，会同时去插数据库，会导致数据库多插数据，或数据混乱的问题。
*/

// 相同的定时任务也就意味着他们往数据库中插入的数据也是相同的数据，显然我们是不需要这么多相同数据的，只需要

/*
1. 设置字段的唯一性，这样从数据库的层次上防止了相同数据的多条插入问题
 */

/*
2. 任务调度
 */

/*
3. 任务拆分
 */

/*
4. 分布式锁
缺点：
	1. 单点问题。
		--- Redlock算法
	2. 执行时间超过了锁的过期时间。

 */

var t int

func main() {

	client := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "",
		DB:       0,
	})

	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	for i := 0; i <= 100; i++ {
		//go add1("aaa")
		go add2("aaa", i)
	}

	//fmt.Println(lock("aaa", 1, client))

	time.Sleep(20 * time.Second)

	result, err := client.Del("task:aaa").Result()
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("del: ", result)

}

func add1(name string)  {
	client := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "",
		DB:       0,
	})

	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	incr, err := client.Incr(name).Result()
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	if incr == 1 {
		task(name, client)
	}
	fmt.Println(incr)
}

// 执行定时任务
func add2(name string, i int) {

	uniqueId := rand.Int()

	client := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "",
		DB:       0,
	})

	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	for {
		_, err = client.Get(fmt.Sprint("task:", name)).Result()
		if err != nil {
			if lock(name, uniqueId, client) {
				task(name, client)
				unLock(name, uniqueId, client)
			}
		} else {
			break
		}
	}

	fmt.Println("定时任务", i, "结束")
}

// 获取分布式锁
func lock(name string, value int, client *redis.Client) bool {
	result, err := client.SetNX(name, value, time.Hour).Result()
	if err != nil {
		fmt.Println(err.Error())
		return false
	}
	return result
}

// 释放分布式锁
func unLock(name string, value int, client *redis.Client)  {
	script := redis.NewScript(`
	if
		redis.call('get', KEYS[1) == ARGV[1
	then
		return redis.call('del', KEYS[1) 
	else 
		return 0 
	end
	`)

	_, err := script.Run(
		client,
		[]string{name},
		value).Result()
	if err != nil {
		fmt.Println(err)
		return
	}

}

// 执行任务
func task(name string, client *redis.Client) {
	t++
	_, err := fmt.Println("执行了", t, "次")
	if err == nil {
		client.Set(fmt.Sprint("task:", name), 1, time.Hour * 23)
	}

}
